package com.screwdriver.jso.processor;

import com.screwdriver.jso.client.annotation.JSOInterface;
import com.screwdriver.jso.processor.utils.TypeUtil;
import com.sun.mirror.apt.AnnotationProcessor;
import com.sun.mirror.apt.AnnotationProcessorEnvironment;
import com.sun.mirror.declaration.AnnotationTypeDeclaration;
import com.sun.mirror.declaration.Declaration;
import com.sun.mirror.declaration.InterfaceDeclaration;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;

@SuppressWarnings("deprecation")
public class OldJSOAnnotationProcessor implements AnnotationProcessor {

    private static final Logger log = Logger.getLogger(OldJSOAnnotationProcessor.class.getCanonicalName());
    private AnnotationProcessorEnvironment oldProcessingEnv;

    public OldJSOAnnotationProcessor(AnnotationProcessorEnvironment env) {
        this.oldProcessingEnv = env;
    }

    @Override
    public void process() {
        log.info("starting processing annotations");
        for (InterfaceDeclaration td : findInterfacesAnnotatedWith(JSOInterface.class)) {
            generateJSO(td);
        }
    }

    private void generateJSO(InterfaceDeclaration td) {
        log.info(String.format("generating JSO for %s", td.getQualifiedName()));
        try {
            tryGenerateJSO(td);
        } catch (IOException e) {
            log.log(Level.SEVERE, String.format("Cannot generate JSO for %s", td.getQualifiedName()), e);
        }
    }

    private void tryGenerateJSO(InterfaceDeclaration root) throws IOException {
        log.info(String.format("trying to generate JSO for %s", root.getQualifiedName()));
        PrintWriter sourceWriter = null;
        try {

            TypeVisitManager typeVisitManager = new TypeVisitManager(root);
            sourceWriter = oldProcessingEnv.getFiler().createSourceFile(composeJSOQualifiedName(root));

            sourceWriter.println(String.format("package %s;", root.getPackage().getQualifiedName() + ".generated"));
            sourceWriter.println();

            sourceWriter.println("import com.google.gwt.core.client.JavaScriptObject;");
            sourceWriter.println(String.format("import %s;", root.getQualifiedName()));
            typeVisitManager.traverseTypeHierachy(new ImportsGeneratingInterfaceVisitor(oldProcessingEnv, sourceWriter));

            sourceWriter.println();
            InterfaceDeclaration superTypeDeclaration = TypeUtil.inferSuperType(root);
            String superTypeName = superTypeDeclaration != null ? composeJSOQualifiedName(superTypeDeclaration) : "JavaScriptObject";
            sourceWriter.println(String.format("public class %1$sJSO extends %2$s implements %1$s {",
                    root.getSimpleName(), superTypeName));

            sourceWriter.println();
            sourceWriter.println(String.format("\t protected %sJSO(){} ", root.getSimpleName()));

            sourceWriter.println();
            typeVisitManager.traverseTypeHierachy(new MethodsGeneratingInterfaceVisitor(oldProcessingEnv, sourceWriter));

            sourceWriter.println();
            sourceWriter.println("}");
            sourceWriter.flush();
        } finally {
            if (sourceWriter != null) {
                sourceWriter.close();
            }
        }

    }

    private String composeJSOQualifiedName(InterfaceDeclaration td) {
        return td.getPackage().getQualifiedName() + ".generated." + td.getSimpleName() + "JSO";
    }

    private Collection<InterfaceDeclaration> findInterfacesAnnotatedWith(Class<JSOInterface> class1) {
        log.info(String.format("finding interfaces annotated with %s ", class1.getCanonicalName()));
        Collection<InterfaceDeclaration> interfaces = new LinkedList<InterfaceDeclaration>();

        Collection<Declaration> declarationsAnnotatedWith = oldProcessingEnv
                .getDeclarationsAnnotatedWith((AnnotationTypeDeclaration) oldProcessingEnv.getTypeDeclaration(class1
                        .getCanonicalName()));
        for (Declaration d : declarationsAnnotatedWith) {
            if (d instanceof InterfaceDeclaration) {
                interfaces.add((InterfaceDeclaration) d);
            } else {
                log.warning(String.format("type %s is not interface. Only interfaces can be annotated with %s ",
                        d.getSimpleName(), class1.getCanonicalName()));
            }
        }
        return interfaces;
    }

}
